package org.dromara.common.social.topiam;

import cn.hutool.core.lang.Dict;
import cn.hutool.core.util.StrUtil;
import com.xkcoding.http.support.HttpHeader;
import lombok.extern.slf4j.Slf4j;
import me.zhyd.oauth.cache.AuthStateCache;
import me.zhyd.oauth.config.AuthConfig;
import me.zhyd.oauth.exception.AuthException;
import me.zhyd.oauth.model.AuthCallback;
import me.zhyd.oauth.model.AuthToken;
import me.zhyd.oauth.model.AuthUser;
import me.zhyd.oauth.request.AuthDefaultRequest;
import me.zhyd.oauth.utils.HttpUtils;
import me.zhyd.oauth.utils.UrlBuilder;
import org.dromara.common.core.utils.SpringUtils;
import org.dromara.common.json.utils.JsonUtils;

import static org.dromara.common.social.topiam.AuthTopiamSource.TOPIAM;


/**
 * @Author 刘武贵
 * @Date 2024/8/15 22:00
 * @Description 功能描述：TopIAM 认证请求
 */
/**
 * AuthTopIamRequest 类继承自 AuthDefaultRequest，用于实现特定的认证请求处理，
 * 主要聚焦于 IAM（Identity and Access Management）相关的认证请求处理。
 */
@Slf4j
public class AuthTopIamRequest extends AuthDefaultRequest {

    /**
     * 从配置文件中获取服务器URL
     * 使用SpringUtils工具类来获取配置信息，具体配置键名为justauth.type.topiam.server-url
     */
    public static final String SERVER_URL = SpringUtils.getProperty("justauth.type.topiam.server-url");

    /**
     * 设定归属域
     */
    public AuthTopIamRequest(AuthConfig config) {
        super(config, TOPIAM);
    }

    /**
     * 构造AuthTopIamRequest对象
     *
     * @param config      认证配置对象，包含认证相关的配置信息
     * @param authStateCache  认证状态缓存对象，用于缓存认证状态
     */
    public AuthTopIamRequest(AuthConfig config, AuthStateCache authStateCache) {
        super(config, TOPIAM, authStateCache);
    }

    /**
     * 获取访问令牌
     *
     * 此方法用于通过授权回调来获取访问令牌，它在后台执行授权流程，并在完成授权后返回访问令牌
     *
     * @param authCallback 授权回调对象，用于处理授权过程中的交互和结果
     * @return 访问令牌对象，包含获取到的访问令牌信息
     */
    @Override
    protected AuthToken getAccessToken(AuthCallback authCallback) {
        // 通过POST请求获取授权码
        String body = doPostAuthorizationCode(authCallback.getCode());
        // 将请求返回的JSON字符串解析为Map对象
        Dict object = JsonUtils.parseMap(body);
        // 检查返回数据是否出错
        checkResponse(object);
        return AuthToken.builder()
            // 设置访问令牌
            .accessToken(object.getStr("access_token"))
            // 设置刷新令牌
            .refreshToken(object.getStr("refresh_token"))
            // 设置ID令牌
            .idToken(object.getStr("id_token"))
            // 设置令牌类型
            .tokenType(object.getStr("token_type"))
            // 设置令牌的作用范围
            .scope(object.getStr("scope"))
            .build();
    }

    /**
     * 通过调用对应API获取用户信息，并构建AuthUser对象
     * 此方法解释了为什么需要调用API来获取用户信息，以及如何处理获取到的信息
     *
     * @param authToken 认证令牌，用于获取用户信息
     * @return 包含用户信息的AuthUser对象
     */
    @Override
    protected AuthUser getUserInfo(AuthToken authToken) {
        // 通过发送HTTP GET请求，使用提供的认证令牌获取用户信息
        String body = doGetUserInfo(authToken);
        // 使用JsonUtils工具将获取的用户信息JSON字符串解析为Map对象
        Dict object = JsonUtils.parseMap(body);
        checkResponse(object);
        return AuthUser.builder()
            // 设置用户唯一标识符
            .uuid(object.getStr("sub"))
            // 设置用户的登录名
            .username(object.getStr("preferred_username"))
            // 设置用户昵称
            .nickname(object.getStr("nickname"))
            // 设置用户头像URL
            .avatar(object.getStr("picture"))
            // 设置用户邮箱
            .email(object.getStr("email"))
            // 设置用户认证令牌
            .token(authToken)
            // 设置用户来源，此处来源为OAuth2提供者
            .source(source.toString())
            .build();
    }

    /**
     * 根据提供的认证令牌获取用户信息
     * 此方法通过HTTP GET请求从远程服务获取用户信息
     * 它使用认证令牌来提供用户身份验证
     *
     * @param authToken 认证令牌，用于获取用户信息
     * @return 用户信息字符串
     */
    @Override
    protected String doGetUserInfo(AuthToken authToken) {
        // 根据配置创建HttpUtils实例，用于发起HTTP请求
        return new HttpUtils(config.getHttpConfig()).get(source.userInfo(), null, new HttpHeader()
            // 添加Content-Type头，指定请求的内容类型为JSON
            .add("Content-Type", "application/json")
            // 添加Authorization头，使用Bearer令牌进行身份验证
            .add("Authorization", "Bearer " + authToken.getAccessToken()),
            // 标记请求不使用缓存
            false).getBody();
    }

    /**
     * 根据提供的状态参数构建授权URL
     * 此方法通过调用父类的authorize方法来生成基础授权URL，并添加自定义的查询参数
     * 主要用于应用程序的授权流程，通过指定的scope来控制授权的权限范围
     *
     * @param state 一个用于保持请求和回调状态的值，用于安全性验证
     * @return 构建完成的授权URL，包含应用的scope参数
     */
    @Override
    public String authorize(String state) {
        // 从父类的authorize方法开始构建URL，并添加自定义的scope查询参数
        return UrlBuilder.fromBaseUrl(super.authorize(state))
            .queryParam("scope", StrUtil.join("%20", config.getScopes()))
            .build();
    }

    /**
     * 检查服务器响应是否包含错误信息
     * 主要用于处理OAuth令牌验证和用户验证时服务器返回的错误
     * 如果服务器响应中包含错误信息（error或message），则抛出AuthException异常
     *
     * @param object 服务器响应的字典对象，可能包含错误信息
     * @throws AuthException 如果响应中存在错误信息，则抛出认证异常，携带错误描述
     */
    public static void checkResponse(Dict object) {
        // oauth/token 验证异常
        if (object.containsKey("error")) {
            throw new AuthException(object.getStr("error_description"));
        }
        // user 验证异常
        if (object.containsKey("message")) {
            throw new AuthException(object.getStr("message"));
        }
    }

}
